Source code for hysop.domain.box

# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


"""Box-shaped domains definition.
"""
import warnings
import numpy as np

from hysop.constants import BoxBoundaryCondition, HYSOP_REAL, HYSOP_DEFAULT_TASK_ID
from hysop.domain.domain import Domain, DomainView
from hysop.tools.decorators import debug
from hysop.tools.numpywrappers import npw
from hysop.tools.htypes import check_instance, first_not_None, to_tuple
from hysop.tools.warning import HysopWarning


[docs] class BoxView(DomainView): __slots__ = ("_domain", "_topology_state")
[docs] @debug def __new__(cls, topology_state, domain=None, **kwds): """Create and initialize a BoxView.""" from hysop.topology.cartesian_topology import CartesianTopologyState check_instance(topology_state, CartesianTopologyState) check_instance(domain, Box, allow_none=True) obj = super().__new__(cls, topology_state=topology_state, domain=domain, **kwds) check_instance(obj._domain, Box) return obj
def __init__(self, topology_state, domain=None, **kwds): super().__init__(topology_state=topology_state, domain=domain, **kwds) def __get_domain_attr(self, name): """Get a transposed domain attribute.""" return self._topology_state.transposed(getattr(self._domain, name)) def _get_length(self): """Box sides lengthes.""" return self.__get_domain_attr("_length") def _get_origin(self): """Position of the lowest point of the box.""" return self.__get_domain_attr("_origin") def _get_end(self): """Position of the greatest point of the box.""" return self.__get_domain_attr("_origin") + self.__get_domain_attr("_length") def _get_lboundaries(self): """Left boundary conditions.""" return self.__get_domain_attr("_lboundaries") def _get_rboundaries(self): """Right boundary conditions.""" return self.__get_domain_attr("_rboundaries") def _get_boundaries(self): """Left and right boundary conditions as a tuple.""" return ( self.__get_domain_attr("_lboundaries"), self.__get_domain_attr("_rboundaries"), ) def _get_periodicity(self): """Numpy array mask, True is axis is periodic, else False.""" periodic = BoxBoundaryCondition.PERIODIC is_lperiodic = self.__get_domain_attr("_lboundaries") == periodic is_rperiodic = self.__get_domain_attr("_rboundaries") == periodic if np.logical_xor(is_lperiodic, is_rperiodic).any(): msg = "Domain periodic boundaries have been mixed with another boundary " msg += "on the same axe." raise RuntimeError(msg) return is_lperiodic
[docs] def long_description(self): """ Return a long description of this Box as a string. """ s = f"{self.full_tag} | {self.dim}D rectangular box domain:" s += f"\n *origin: {self.origin}" s += f"\n *max_pos: {self.end}" s += f"\n *length: {self.length}" s += f"\n *left boundary conditions: {self.lboundaries.tolist()}" s += f"\n *right boundary conditions: {self.rboundaries.tolist()}" s += "\n" return s
[docs] def short_description(self): """ Return a short description of this Box as a string. """ return "{} (O=[{}], L=[{}], BC=[{}], current_task={})".format( self.full_tag, ",".join(f"{val:1.1f}" for val in self.origin), ",".join(f"{val:1.1f}" for val in self.length), self.format_boundaries(), self.current_task(), )
[docs] def format_boundaries(self): return ",".join(f"{str(lb)}/{str(rb)}" for (lb, rb) in zip(*self.boundaries))
length = property(_get_length) origin = property(_get_origin) end = property(_get_end) lboundaries = property(_get_lboundaries) rboundaries = property(_get_rboundaries) boundaries = property(_get_boundaries) periodicity = property(_get_periodicity)
[docs] class Box(BoxView, Domain): """ Box-shaped domain description. """ @debug def __init__( self, length=None, origin=None, dim=None, lboundaries=None, rboundaries=None, **kwds, ): super().__init__( length=length, origin=origin, dim=dim, lboundaries=lboundaries, rboundaries=rboundaries, domain=None, topology_state=None, **kwds, )
[docs] @debug def __new__( cls, length=None, origin=None, dim=None, lboundaries=None, rboundaries=None, **kwds, ): """ Create or get an existing Box from a dimension, length and origin with specified left and right boundary conditions. Parameters ---------- length : array like of float, optional Box sides lengthes. Default = [1.0, ...] origin: array like of float, optional Position of the lowest point of the box. Default [0.0, ...] dim: int, optional Dimension of the box. lboundaries: array_like of BoxBoundaryCondition Left boundary conditions. rboundaries: array_like of BoxBoundaryCondition Right boundary conditions. Attributes ---------- dim: int Dimension of the box. length : np.ndarray of HYSOP_REAL Box sides lengthes. origin: np.ndarray of HYSOP_REAL Position of the lowest point of the box. end: np.ndarray of HYSOP_REAL Position of the greatest point of the box. lboundaries: np.ndarray of BoxBoundaryCondition Left boundary conditions. rboundaries: np.ndarray of BoxBoundaryCondition Right boundary conditions. boundaries: tuple of np.ndarray of BoxBoundaryCondition Left and right boundary conditions as a tuple. periodicity: np.ndarray of bool Numpy array mask, True is axis is periodic, else False. """ from hysop.topology.cartesian_topology import CartesianTopologyState check_instance(dim, int, minval=1, allow_none=True) check_instance( length, (np.ndarray, list, tuple), values=(np.integer, int, float), allow_none=True, ) check_instance( origin, (np.ndarray, list, tuple), values=(np.integer, int, float), allow_none=True, ) check_instance( lboundaries, (np.ndarray, list, tuple), values=BoxBoundaryCondition, allow_none=True, ) check_instance( rboundaries, (np.ndarray, list, tuple), values=BoxBoundaryCondition, allow_none=True, ) if (length is None) and (origin is None) and (dim is None): msg = "At least one of the following parameters should be given: length, origin, dim." raise ValueError(msg) dim = first_not_None(dim, 0) length = to_tuple(first_not_None(length, 1.0)) origin = to_tuple(first_not_None(origin, 0.0)) dim = max(dim, len(length), len(origin)) if len(length) == 1: length *= dim if len(origin) == 1: origin *= dim length = npw.asrealarray(length) origin = npw.asrealarray(origin) check_instance(length, np.ndarray, size=dim) check_instance(origin, np.ndarray, size=dim) assert (length >= 0.0).all(), "length < 0" lboundaries = npw.asarray( first_not_None(lboundaries, (BoxBoundaryCondition.PERIODIC,) * dim) ) rboundaries = npw.asarray( first_not_None(rboundaries, (BoxBoundaryCondition.PERIODIC,) * dim) ) assert lboundaries.size == rboundaries.size == dim for i, (lb, rb) in enumerate(zip(lboundaries, rboundaries)): if (lb == BoxBoundaryCondition.PERIODIC) ^ ( rb == BoxBoundaryCondition.PERIODIC ): msg = ( f"FATAL ERROR: Periodic BoxBoundaryCondition mismatch on axis {i}." ) msg += "\nGot:" msg += f"\n *lboundaries: {lboundaries}" msg += f"\n *rboundaries: {rboundaries}" raise ValueError(msg) nper = npw.sum(lboundaries == BoxBoundaryCondition.PERIODIC) if (nper > 0) and not all(lboundaries[:nper] == BoxBoundaryCondition.PERIODIC): msg = "\nPeriodic boundary conditions should be on last axes (ie. Z,Y,X,...), got " msg += f"periodicity {lboundaries==BoxBoundaryCondition.PERIODIC}." msg += "\nAll spectral solvers (including Poisson solvers) will fail with an error." msg += "\nPlease permute axes prior to problem description." msg += "\nSpecified boundaries were:" msg += f"\n *lboundaries: {lboundaries}" msg += f"\n *rboundaries: {rboundaries}" warnings.warn(msg, HysopWarning) # double check types, to be sure RegisteredObject will work as expected check_instance(dim, int) check_instance(length, np.ndarray, dtype=HYSOP_REAL) check_instance(origin, np.ndarray, dtype=HYSOP_REAL) check_instance(lboundaries, np.ndarray, dtype=object) check_instance(rboundaries, np.ndarray, dtype=object) npw.set_readonly(length, origin, lboundaries, rboundaries) topology_state = CartesianTopologyState(dim, HYSOP_DEFAULT_TASK_ID) obj = super().__new__( cls, length=length, origin=origin, dim=dim, lboundaries=lboundaries, rboundaries=rboundaries, domain=None, topology_state=topology_state, **kwds, ) if not obj.obj_initialized: obj._length = length obj._origin = origin obj._lboundaries = lboundaries obj._rboundaries = rboundaries return obj
[docs] def view(self, topology_state): """Return a view of this domain altered by some topology_state.""" return BoxView(domain=self, topology_state=topology_state)